/*********************************************************
 * Project Atlantis Physical Memory Manager              *
 * This code is largely based on or copied from the      *
 * BrokenThorn tutorial 17, because the programmer was   *
 * too lazy to write his own.						     *
 *********************************************************/

#include "pmm.h"

uint32_t  pmm_memory_size;
uint32_t  pmm_used_blocks;
uint32_t  pmm_max_blocks;
uint32_t* pmm_block_map;

inline void pmm_map_set(uint32_t bit){
	pmm_block_map[bit / 32] |= 1 << (bit % 32);
}

inline void pmm_map_clear(uint32_t bit){
	pmm_block_map[bit / 32] &= ~ (1 << (bit % 32));
}

inline char pmm_map_test(uint32_t bit){
	return (char) (pmm_block_map[bit / 32] & (1 << (bit % 32)));
}

int pmm_map_ffree() {
	int bit;
	int j;
	uint32_t i;
	for (i=0; i < pmm_max_blocks / 32; i++)
		if (pmm_block_map[i] != 0xffffffff)
			for (j=0; j<32; j++) {
				bit = 1 << j;
				if (! (pmm_block_map[i] & bit) )
					return i*4*8+j;
			} 
	return -1;
}

int pmm_map_ffree_s (size_t size) {
	int startingBit;
	int j;
	int bit;
	uint32_t i;
    uint32_t free;
	uint32_t count;
	if (size==0)
		return -1;
	if (size==1)
		return pmm_map_ffree();
	for (i=0; i<pmm_max_blocks /32; i++)
		if (pmm_block_map[i] != 0xffffffff)
			for (j=0; j<32; j++) {	//! test each bit in the dword
				bit = 1<<j;
				if (!(pmm_block_map[i] & bit) ) {
					startingBit = i*32;
					startingBit+=bit;		//get the free bit in the dword at index i
					free=0; //loop through each bit to see if its enough space
					for (count=0; count<=size;count++) {
						if (! pmm_map_test (startingBit+count) )
							free++;	// this bit is clear (free frame)
						if (free==size)
							return i*4*8+j; //free count==size needed; return index
					}
				}
			}
	return -1;
}

uint32_t pmm_get_free_block_count(){
	return pmm_max_blocks - pmm_used_blocks;
}
uint32_t pmm_get_size(){
	return pmm_max_blocks / 8;
}

void pmm_init (size_t memSize, physical_addr bitmap) { 
	pmm_memory_size	=	memSize;
	pmm_block_map	=	(uint32_t*) bitmap;
	pmm_max_blocks	=	(pmm_memory_size*1024) / PMM_BLOCK_SIZE;
	pmm_used_blocks	=	pmm_max_blocks;
	memset (pmm_block_map, 0xff, pmm_max_blocks / 8);
}

void pmm_init_region (physical_addr base, size_t size) { 
	int align = base / PMM_BLOCK_SIZE;
	int blocks = size / PMM_BLOCK_SIZE; 
	for (; blocks>0; blocks--) {
		pmm_map_clear(align++);
		pmm_used_blocks--;
	} 
	pmm_map_set(0);
}

void pmm_deinit_region (physical_addr base, size_t size) { 
	int align = base / PMM_BLOCK_SIZE;
	int blocks = size / PMM_BLOCK_SIZE; 
	for (; blocks>0; blocks--) {
		pmm_map_set (align++);
		pmm_used_blocks++;
	}
}

void* pmm_alloc_block() { 
	int frame;
	physical_addr addr;
	if (pmm_get_free_block_count() <= 0)
		return 0;	//out of memory 
	frame = pmm_map_ffree(); 
	if (frame == -1)
		return 0;	//out of memory 
	pmm_map_set(frame); 
	addr = frame * PMM_BLOCK_SIZE;
	pmm_used_blocks++; 
	return (void*)addr;
}

void* pmm_alloc_blocks(size_t size) { 
	uint32_t count;
	int frame;
	physical_addr addr;
	if (pmm_get_free_block_count() <= size)
		return 0;	//out of memory 
	frame = pmm_map_ffree_s(size); 
	if (frame == -1)
		return 0;	//out of memory 
	for (count = 0;count < size; count++)
		pmm_map_set(frame+count); 
	addr = frame * PMM_BLOCK_SIZE;
	pmm_used_blocks+=size; 
	return (void*)addr;
}

void pmm_free_block (void* p) { 
	physical_addr addr = (physical_addr)p;
	int frame = addr / PMM_BLOCK_SIZE; 
	pmm_map_clear(frame); 
	pmm_used_blocks--;
} 
void pmm_free_blocks(void* p, size_t size) { 
	uint32_t count;
	physical_addr addr = (physical_addr)p;
	int frame = addr / PMM_BLOCK_SIZE; 
	for (count = 0;count < size; count++)
		pmm_map_clear(frame+count); 
	pmm_used_blocks-=size;
}